using System;
using SharpMud.MudLib.SAMPLE;
using SharpMud.MudLib.SAMPLE.Net;
using SharpMud.Server.Systems.Net;
using System.Collections;
using System.Collections.Specialized;
using SharpMud.Net;

namespace SharpMud.MudLib.SAMPLE.Net
{
	/// <summary>
	/// This class is responsible for managing a character's session over a telnet
	/// connection. It abstracts game events that happen to the player into
	/// Telnet's terminal-style interaction methods
	/// </summary>
	public class TelnetPlayerSession : SharpMud.Net.Telnet.Session.TelnetSessionBase, IHasSessionState<TelnetPlayerSession>
	{
		#region Private Variables
		private System.Collections.Generic.Dictionary<string,object>	_DynamicData;
		private SessionStateStack<TelnetPlayerSession>					_SessionState;
		private Entities.PlayerCharacter								_CurrentCharacter;
		private Entities.PlayerCharacter								_CharacterInCreation;
		private string													_LogonName;
		#endregion

		#region Constructors
		public TelnetPlayerSession()
			:base()
		{
			_DynamicData = new System.Collections.Generic.Dictionary<string,object>();
			_SessionState = new SessionStateStack<TelnetPlayerSession>(@"\");
			_SessionState.Initialize(this,null);
		}
		#endregion

		#region Public Properties
		/// <summary>
		/// This property gets the name associated with this session's player. Setting of this property
		/// is not allowed after successful logon, or after completed character creation.
		/// </summary>
		public string PlayerName
		{
			get
			{
				if(IsInCreation)
				{
					return this.CharacterInCreation.PlayerName;
				}
				if(IsLoggedOn)
				{
					return this.CurrentCharacter.PlayerName;
				}
				else
					return _LogonName;
			}
			set
			{
				if(IsInCreation)
				{
					if(this._CurrentCharacter.PlayerName!=String.Empty)
						throw new InvalidOperationException();
				}
				if(IsLoggedOn)
				{
					throw new InvalidOperationException();
				}
				else
					_LogonName=value;
			}
		}
		/// <summary>
		/// This property determines if the session represents a logged on user or not.
		/// </summary>
		public bool IsLoggedOn
		{
			get
			{
				if(this._CurrentCharacter!=null)
					return true;
				else
					return false;
			}
		}
		/// <summary>
		/// This property determines if the user of the session is in character creation or not
		/// </summary>
		public bool IsInCreation
		{
			get
			{
				if(this._CurrentCharacter==null && this._CharacterInCreation!=null)
					return true;
				else
					return false;
			}
		}
		/// <summary>
		/// Gets the currently logged on character for the session
		/// </summary>
		public Entities.PlayerCharacter CurrentCharacter
		{
			get
			{
				return _CurrentCharacter;
			}
		}
		/// <summary>
		/// Gets or sets the character, if there is one, that is in creation via this session
		/// </summary>
		public Entities.PlayerCharacter CharacterInCreation
		{
			get
			{
				return _CharacterInCreation;
			}
			set
			{
				_CharacterInCreation = value;
			}
		}

		/// <summary>
		/// The mudserver, translated into local mudlib type
		/// </summary>
		public MudLibRoot HostMud
		{
			get
			{
				return (MudLibRoot)this.Host.Host;
			}
		}
		/// <summary>
		/// Gets the object that allows for dynamic data (string key, object value) to be
		/// left on the session
		/// </summary>
		public System.Collections.Generic.IDictionary<string,object> DynamicData
		{
			get
			{
				return _DynamicData;
			}
		}
		/// <summary>
		/// Gets the topmost SessionStateStack of this session, representing the overall
		/// state of this sesion
		/// </summary>
		public ISessionStateStack<TelnetPlayerSession> SessionState
		{
			get
			{
				return ((ISessionStateStack<TelnetPlayerSession>)this._SessionState);
			}
		}
		public string RemoteHostName
		{
			get
			{
				return System.Net.Dns.GetHostByAddress(this.Connection.RemoteEndpoint.Address).HostName;
			}
		}
		public string LocalHostName
		{
			get
			{
				return System.Net.Dns.GetHostByAddress(this.Connection.LocalEndpoint.Address).HostName;
			}
		}
		#endregion

		#region Public Methods
		/// <summary>
		/// Migrates the character that is in creation on this session into the character that this
		/// session is logged on as. Adds the player to the Player Metabase.
		/// Does not integrate the character into the game world!
		/// </summary>
		public void MigrateCreationCharacter()
		{
			if(this.IsInCreation)
			{
				this._CurrentCharacter = this._CharacterInCreation;
				this._CharacterInCreation = null;
				HostMud.PlayerSystem.Players.AddPlayer(this._CurrentCharacter);
				this._CurrentCharacter.Initialize(HostMud.PlayerSystem.Players);

				this._CurrentCharacter.Session = this;
			}
		}

		protected override void _Pulse()
		{
			base._Pulse ();
			lock(this)
			{
                try
                {
                    SessionState.Pulse();
                }
                catch (System.IO.IOException e)
                {
                    this.RegisterConnectionFailed(e);
                    return;
                }
			}
		}

        protected override void _Dispose()
        {
            this._SessionState.Clear();
        }

		/// <summary>
		/// Attempt to logon this session as a particular user.
		/// </summary>
		/// <param name="userName">The user's name</param>
		/// <param name="password">The user's password</param>
		/// <exception cref="Entities.PasswordMisMatchException">Thrown if the password given is incorrect</exception>
		/// <exception cref="Entities.PlayerNotFoundException">Thrown if the user specified does not exist</exception>
		public void Logon(string userName, string password)
		{
			if(userName==String.Empty) throw new ArgumentNullException("userName");
			if(!HostMud.PlayerSystem.Players.PlayerExists(userName))
			{
				throw new Entities.PlayerNotFoundException(userName);
			}
			Entities.PlayerCharacter player = HostMud.PlayerSystem.Players.GetPlayer(userName,true,false);
			if(player.HasPassword(password)==TriState.False)
			{
				throw new Entities.PasswordMismatchException(userName);		
			}
			else
			{
				this.EliminateRedundantSessions(player);
				this._CurrentCharacter = player;
				player.Session = this;
				this.HostMud.PlayerSystem.RegisterPlayerLogon(player);
			}
		}

		protected void EliminateRedundantSessions(Entities.PlayerCharacter player)
		{
			foreach(TelnetPlayerSession tps in HostMud.TelnetServer.GetSessions())
			{
				if(tps.CurrentCharacter!=null && tps!=this && tps.CurrentCharacter==player)
				{
					tps.WriteLine();
					tps.WriteLine("This session is now redundant. Disconnecting...");
					tps.WriteLine();
					tps.Flush();
					
					tps.Log.Warn("Session now redundant. Disconnecting...");
					tps.Logoff();
					tps.Connection.Close();
					
					this.WriteLine();
					this.WriteLine("Pre-existing session, from '" + tps.Connection.RemoteEndpoint.ToString() + "', closed.");
					this.WriteLine();
					this.Flush();

					this.Log.Warn("pre-existing [Session:" + tps.SessionID + "] closed.");

					break;
				}
			}
		}

		/// <summary>
		/// Detaches this session from the player character it is logged on as
		/// </summary>
		public void Logoff()
		{
			if(this.IsLoggedOn)
			{
				Entities.PlayerCharacter p = (Entities.PlayerCharacter)this._CurrentCharacter;
				this._CurrentCharacter.Session = null;
				this._CurrentCharacter = null;
				this.HostMud.PlayerSystem.RegisterPlayerLogoff(p);
			}
		}

		#region Write (extensions)
		public void Write(SAMPLE.Messaging.Act.IActMessage actMessage)
		{
			this.Write(this.NewLine + actMessage.ToAbsoluteValue());
		}

		public void Write(SAMPLE.Messaging.Act.IMXPEnhancedMessage actMessage)
		{
			this.Write(this.NewLine + actMessage.EvaluateMXPEnhancedMessageFor(this));
		}

		public void WriteLine(SAMPLE.Messaging.Act.IActMessage actMessage)
		{
			this.Write(this.NewLine + actMessage.ToAbsoluteValue() + this.NewLine);
		}

		public void WriteLine(SAMPLE.Messaging.Act.IMXPEnhancedMessage actMessage)
		{
			this.Write(this.NewLine + actMessage.EvaluateMXPEnhancedMessageFor(this) + this.NewLine);
		}
		#endregion

		#endregion

		/// <summary>
		/// Initializes the Telnet Session to a default state
		/// </summary>
		protected override void _Initialize()
		{
			base._Initialize();

			//Setup the session state stack with default entries.
			//First it runs the state for new connections. It must add additional
			//states to the stack.
			//After they are all done,the second state we put on the stack here will
			//close it
			
			//Stack Push-Pop, FILO

			this.Connection.Disconnected +=new SharpMud.Net.ConnectionEventHandler(Connection_Disconnected);
			
			// 2
			this.SessionState.Push(new SessionStates.CloseConnectionNow());
			// 1
			this.SessionState.Push(new SessionStates.NewConnection());
		}

		private void Connection_Disconnected(SharpMud.Net.INetworkConnection sender, SharpMud.Net.ConnectionEventArgs e)
		{
			Log.Info("Client Disconnected.");
			this.SessionState.Clear();
			Log.Debug("Session State(s) cleared.");
		}

		#region Tell
		private Entities.ICharacter _LastTellSender;
		private Entities.ICharacter	_LastTellRecipient;

		public void WriteLine(Messaging.Act.BuiltIn.Communication.Tell.ToRecipient tellMessage)
		{
			this._LastTellSender = tellMessage.TellSender;
			this.WriteLine((Messaging.Act.IActMessage)tellMessage);
		}

		public void WriteLine(Messaging.Act.BuiltIn.Communication.Tell.AnonymouslyToRecipient anonymousTellMessage)
		{
			this._LastTellSender = anonymousTellMessage.TellSender;
			this.WriteLine((Messaging.Act.IActMessage)anonymousTellMessage);
		}

		public void WriteLine(Messaging.Act.BuiltIn.Communication.Tell.ToSender tellMessage)
		{
			this._LastTellRecipient = tellMessage.TellRecipient;
			this.WriteLine((Messaging.Act.IActMessage)tellMessage);
		}

		public void DoTellReply(string message)
		{
			if(_LastTellSender==null)
				throw new InvalidOperationException();
			if(_LastTellSender is Entities.PlayerCharacter)
			{
				//Send tell reply to recipient
				(_LastTellSender as Entities.PlayerCharacter).Session.WriteLine(new Messaging.Act.BuiltIn.Communication.Tell.ToRecipient(this.CurrentCharacter,message,_LastTellSender));
                this.Write(new Messaging.Act.BuiltIn.Communication.Tell.ToSender(this.CurrentCharacter,message,_LastTellSender));
			}
			else
			{
				throw new InvalidOperationException();
			}
		}

		public void DoRetell(string message)
		{
			if(_LastTellRecipient==null)
				throw new InvalidOperationException();
			if(_LastTellRecipient is Entities.PlayerCharacter)
			{
				//Send tell reply to recipient
				(_LastTellRecipient as Entities.PlayerCharacter).Session.WriteLine(new Messaging.Act.BuiltIn.Communication.Tell.ToRecipient(this.CurrentCharacter,message,_LastTellRecipient));
				this.Write(new Messaging.Act.BuiltIn.Communication.Tell.ToSender(this.CurrentCharacter,message,_LastTellRecipient));
			}
			else
			{
				throw new InvalidOperationException();
			}
		}
		#endregion

        #region IHasSessionState Members

        SharpMud.Net.ISessionStateStack SharpMud.Net.IHasSessionState.SessionState
        {
            get { return ((SharpMud.Net.ISessionStateStack)this._SessionState); }
        }

        #endregion
    }
}
